<?php
/**
 *------------------------------------------------
 * Author: YYT[QQ:375776626]
 *------------------------------------------------
 */

class DbPDO extends Db
{
    private static $_instance = null;

    public static function getInstance()
    {
        if (is_null(self::$_instance)) self::$_instance = new self();
        return self::$_instance;
    }

    private static $_connect = array();

    private function _connect()
    {
        $connectId = md5(serialize($this->config));
        if (isset(self::$_connect[$connectId])) return self::$_connect[$connectId];
        if (!$this->config['db_dsn']) {
            if (!$this->config['db_type']) {
                throw new Exception(__METHOD__.' [未定义数据库类型]');
            }
            $dsn = $this->config['db_type'].':';
            $dsn .= 'host='.$this->config['db_host'];
            $dsn .= ';dbname='.$this->config['db_name'];
            $dsn .= ';port='.$this->config['db_port'];
        } else {
            $dsn = $this->config['db_dsn'];
        }

        if ($this->config['db_type'] == 'sqlite') { //避免自动创建sqlite文件
            $file = ltrim($this->config['db_dsn'], 'sqlite:');
            if (!is_file($file))
                throw new Exception(__METHOD__.' [sqlite数据库不存在: '.$file.']');
        }
        try{
            self::$_connect[$connectId] = new PDO($dsn,
                $this->config['db_user'],
                $this->config['db_password'],
                array(PDO::ATTR_PERSISTENT => $this->config['db_long_connect']));
            self::$_connect[$connectId]->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //让PDO抛出异常
            if ($this->config['db_type'] == 'mysql') {
                self::$_connect[$connectId]->query('SET NAMES '.$this->config['db_charset']);
            }
            return self::$_connect[$connectId];
        } catch (Exception $e) {
            throw new Exception(__METHOD__.' [数据库连接失败: '.$e->getMessage().']');
        }
    }

    public function escapeString($string)
    {
        $PDO = $this->_connect();
        return substr($PDO->quote($string), 1, -1);
    }

    public function query($sql, $method = '')
    {
        $startTime = microtime(true);
        $PDO = $this->_connect();
        $method = strtolower($method);
        try{
            $sql = $this->parse('prefix', $sql);
            $stmt = $PDO->prepare($sql);
            $result = $stmt->execute();
            if (!$method) return $result;
            $stmt->setFetchMode(PDO::FETCH_ASSOC);
            $result = array();
            switch ($method) {
                case 'fetch':
                    $result = $stmt->fetch();
                    break;
                case 'fetchall':
                    $result = $stmt->fetchAll();
                    break;
                case 'count':
                    $result = $stmt->fetchColumn();
                    break;
                case 'insert':
                    $result = $PDO->lastInsertId();
                    break;
                case 'update':
                case 'delete':
                    $result = $stmt->rowCount();
                    break;
            }
            self::$countQuery++;
            $stopTime = microtime(true);
            Web::debug('[用时<font color="red">'.round(($stopTime - $startTime), 4).'</font>秒]: '.$sql);
            return $result === false ? array() : $result;
        } catch (Exception $e) {
            throw new Exception(__METHOD__. '[SQL错误: '.$sql.']<br />错误提示: '.$e->getMessage());
        }
    }

    public function getFields()
    {
        if (!$this->table) return;

        $dbname = '';
        if (!empty($this->config['db_name']) && !stristr($this->table, $this->config['db_name'])) {
            $dbname = $this->config['db_name'].'.';
        }
        $cacheFile = Web::config('cache_path').'/db/'.$dbname.$this->table.'.php';
        if (is_file($cacheFile)) {
            return unserialize(str_replace('<?php exit();//', '', file_get_contents($cacheFile)));
        }
        $startTime = microtime(true);
        $PDO = $this->_connect();
        try{
            $fields = array();
            switch ($this->config['db_type']) {
                case 'mysql':
                    $sql = 'DESC '.$this->table;
                    $stmt = $PDO->prepare($sql);
                    $stmt->execute();
                    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                        if ($row['Key'] == 'PRI') {
                            $fields['pk'] = strtolower($row['Field']);
                        } else {
                            $fields[] = strtolower($row['Field']);
                        }
                    }
                    break;
                case 'sqlite':
                    $sql = 'PRAGMA table_info('.$this->table.')';
                    $stmt = $PDO->prepare($sql);
                    $stmt->execute();
                    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                        if (!empty($row['pk'])) {
                            $fields['pk'] = strtolower($row['name']);
                        } else {
                            $fields[] = strtolower($row['name']);
                        }
                    }
                    break;
                default:
                    throw new Exception(__METHOD__.' [暂不支持获取'.$this->config['db_type'].'数据库表结构]');
            }
            //如果不存在主键，则提取第一个字段为主键
            if (!array_key_exists('pk', $fields)) $fields['pk'] = array_shift($fields);
            self::$countQuery++;
            if (!empty($fields['pk'])) {
                $stopTime = microtime(true);
                Web::debug('[用时<font color="red">'.round(($stopTime - $startTime), 4).'</font>秒]: '.$sql, 2);
                Web::makeDir(dirname($cacheFile));
                file_put_contents($cacheFile, '<?php exit();//'.serialize($fields));
                return $fields;
            }
        } catch (Exception $e) {
            throw new Exception(__METHOD__.' [获取表'.$this->table.'字段失败: '.$e->getMessage().']');
        }
    }

    public function beginTransaction()
    {
        $PDO = $this->_connect();
        $PDO->beginTransaction();
    }

    public function commit()
    {
        $PDO = $this->_connect();
        $PDO->commit();
    }

    function rollBack()
    {
        $PDO = $this->_connect();
        $PDO->rollBack();
    }

    public function lastInsertId()
    {
        $PDO = $this->_connect();
        return $PDO->lastInsertId();
    }

    public function getVersion()
    {
        $PDO = $this->_connect();
        return $PDO->getAttribute(PDO::ATTR_SERVER_VERSION);
    }
}